package com.ctshk.auth.controller;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.alibaba.fastjson.JSONObject;
import com.ctshk.common.annotations.OperationLogAnnotation;
import com.ctshk.common.constant.RedisConstants;
import com.ctshk.common.dto.Result;
import com.ctshk.common.enums.GlobalConstants;
import com.ctshk.common.enums.OperationLogModel;
import com.ctshk.common.enums.OperationLogType;
import com.ctshk.common.enums.SystemError;
import com.ctshk.common.exception.BusinessException;
import com.ctshk.common.mail.core.MailTemplate;
import com.ctshk.common.model.TokenUser;
import com.ctshk.common.utils.MD5Util;
import com.ctshk.common.utils.RSAUtil;
import com.ctshk.common.web.support.BaseController;
import com.ctshk.rpc.system.dto.CaptchaDTO;
import com.ctshk.rpc.system.dto.SysMenuButtonDTO;
import com.ctshk.rpc.system.dto.UserDTO;
import com.ctshk.rpc.system.req.LoginReq;
import com.ctshk.rpc.system.req.ResetNewMyPasswordReq;
import com.ctshk.rpc.system.req.ResetPasswordReq;
import com.ctshk.rpc.system.service.ISysMenuService;
import com.ctshk.rpc.system.service.ISysUserService;
import com.ctshk.rpc.system.service.IValidateService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

import javax.mail.MessagingException;
import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Api(value = "Auth认证", tags = "注册登录")
@Slf4j
@RefreshScope
@RestController
public class AuthController extends BaseController {

    @Value("${auth.user.token.timeout.sapp}")
    private Long tokenTimeoutSapp;

    @Value("${email.template.reset-password-text}")
    private String resetPasswordTest;

    @Value("${email.template.img-path}")
    private String imgPath;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private MailTemplate mailTemplate;

    @DubboReference
    private ISysUserService userService;

    @DubboReference
    private IValidateService validateService;

    @DubboReference
    private ISysMenuService sysMenuService;

    @ApiOperation(value = "密码登录", notes = "账号密码登录")
    @OperationLogAnnotation(operModul = OperationLogModel.USER, operType = OperationLogType.LOGIN, operTitle = "用户登录", operDesc = "密码登录")
    @PostMapping("/login")
    public Result<UserDTO> login(@RequestBody @Valid LoginReq loginReq) {

        String key = RedisConstants.ACCOUNT_LOCKOUT_TIMES + loginReq.getUsername();
        Object lockoutCount = redisTemplate.opsForValue().get(key);
        if (null != lockoutCount && Integer.parseInt(lockoutCount.toString()) >= 2) {
            validateService.check(loginReq.getKey(), loginReq.getCode());
        }

        UserDTO u = new UserDTO();
        u.setUsername(loginReq.getUsername());
        u.setPassword(loginReq.getPassword());

        u = userService.login(u);
        boolean isAdmin = GlobalConstants.Role.isAdmin(u.getUsername());
        Long uid = u.getId();
        // 获取当前账号的菜单数组
        log.info("【鉴权-登录】查询所有菜单, uid:{}, isAdmin:{}", uid, isAdmin);
        List<Object> menuIds = sysMenuService.queryAllByUserId(null, uid, isAdmin);
        Map<Object, List> map = new HashMap<>();
        // 遍历获取当前菜单下的按钮数组
        log.info("【鉴权-登录】查询所有菜单的按钮, uid:{}, menuId size:{}", uid, menuIds.size());
        List<SysMenuButtonDTO> buttons = sysMenuService.queryActionButtonAllIds(uid, isAdmin, StringUtils.join(menuIds, ","));
        log.info("【鉴权-登录】查询所有菜单的按钮, uid:{}, buttons size:{}", uid, buttons.size());
        menuIds.forEach(menuId -> {
            List<Long> mButtonIds = new ArrayList<>();
            buttons.forEach(button -> {
                if (Objects.equals(menuId, button.getParentId())) {
                    mButtonIds.add(button.getId());
                }
            });
            map.put(menuId, mButtonIds);
        });
        /*log.info("【鉴权-登录】查询所有菜单, uid:{}, isAdmin:{}", uid, isAdmin);
        List<Object> menuIds = sysMenuService.queryAllByUserId(null, uid, isAdmin);
        Map<Object, List> map = new HashMap<>();
        // 遍历获取当前菜单下的按钮数组
        menuIds.forEach(menuId -> {
            map.put(menuId, sysMenuService.queryActionButtonIds(Long.valueOf(String.valueOf(menuId)), uid, isAdmin));
        });*/
        // 获取当前账号的菜单数组插入
        u.setMenuIds(menuIds);
        // 获取当前账号的菜单下的按钮数组插入
        u.setButtonIds(map);

        TokenUser tokenUser = new TokenUser();
        tokenUser.setId(u.getId());
        tokenUser.setUsername(u.getUsername());
        tokenUser.setNickname(u.getFullName());
        tokenUser.setAvatar(u.getAvatar());
        tokenUser.setEmployeeNumber(u.getEmployeeNumber());
        tokenUser.setLastLoginTime(LocalDateTime.now());

        log.info("用户token对象信息[{}]", tokenUser);
        String token = RSAUtil.encryptStr(JSONObject.toJSONString(tokenUser));
        String tokeyKey = RedisConstants.TOKEN_USER_KEY + u.getId();
        //登录成功后清除密码错误的次数
        String lockCountKey = RedisConstants.ACCOUNT_LOCKOUT_TIMES + u.getUsername();
        if (redisTemplate.hasKey(lockCountKey)) {
            redisTemplate.delete(lockCountKey);//有值就删除锁定次数
        }
        redisTemplate.opsForValue().set(tokeyKey, token, tokenTimeoutSapp, TimeUnit.MINUTES);
        getResponse().addHeader(RedisConstants.JWT_TOKEN_HEADER, RedisConstants.JWT_TOKEN_PREFIX + token);
        return Result.success(u);
    }

    @ApiOperation(value = "退出登录", notes = "注销退出登录")
    @OperationLogAnnotation(operModul = OperationLogModel.USER, operType = OperationLogType.LOGOUT, operTitle = "退出登录", operDesc = "注销退出登录")
    @GetMapping("/logout")
    public Result logout() {
        Object token = getRequest().getHeaders(RedisConstants.JWT_TOKEN_HEADER).nextElement();
        String tokenStr = String.valueOf(token).replace(RedisConstants.JWT_TOKEN_PREFIX, Strings.EMPTY);
        String tokenDecrypt = RSAUtil.decryptStr(tokenStr);
        TokenUser tokenUser = JSONObject.parseObject(tokenDecrypt, TokenUser.class);

        String tokeyKey = RedisConstants.TOKEN_USER_KEY + tokenUser.getId();
        redisTemplate.delete(tokeyKey);

        return Result.success();
    }

    @ApiOperation(value = "修改密码", notes = "修改密码-此方法需登录账号")
    @OperationLogAnnotation(operModul = OperationLogModel.USER, operType = OperationLogType.UPDATE, operTitle = "重置密碼", operDesc = "重置密碼")
    @PostMapping("/reset/password")
    public Result reset(@RequestBody @Valid ResetPasswordReq resetPasswordReq) {

        if (!StringUtils.equals(resetPasswordReq.getNewPassword(), resetPasswordReq.getConfirmPassword())) {
            return Result.failed(SystemError.USER_1012);
        }

        UserDTO u = userService.loadUserByParam(getCurrentUser(getRequest()).getId(), 1);

        if (!BCrypt.checkpw(resetPasswordReq.getOldPassword(), u.getPassword())) {
            return Result.failed(SystemError.USER_1011);
        }

        if (BCrypt.checkpw(resetPasswordReq.getNewPassword(), u.getPassword())) {
            return Result.failed(SystemError.USER_1013);
        }

        if (!userService.verifyTheFormat(resetPasswordReq.getNewPassword())) {
            return Result.failed(SystemError.USER_1022);
        }
        try {
            userService.resetPassword(u.getId(), resetPasswordReq.getNewPassword());
        } catch (Exception e) {
            return Result.failed(SystemError.USER_1014);
        }

        return Result.success();
    }

    @ApiOperation(value = "设置新密码", notes = "设置新密码-此方法无需登录通过邮箱认证")
    @OperationLogAnnotation(operModul = OperationLogModel.USER, operType = OperationLogType.UPDATE, operTitle = "重置密碼", operDesc = "重置密碼-此方法无需登录通过邮箱认证")
    @PostMapping("/reset/new/password")
    public Result resetPass(@RequestBody @Valid ResetNewMyPasswordReq req) {
        if (!StringUtils.equals(req.getNewPassword(), req.getConfirmPassword())) {
            return Result.failed(SystemError.USER_1012);
        }
        if (!redisTemplate.hasKey(req.getKey())) {
            return Result.failed(SystemError.USER_1025);
        }
        Object id = redisTemplate.opsForValue().get(req.getKey());
        UserDTO u = userService.loadUserByParam(id, 1);
        if (BCrypt.checkpw(req.getNewPassword(), u.getPassword())) {
            return Result.failed(SystemError.USER_1013);
        }
        if (!userService.verifyTheFormat(req.getNewPassword())) {
            return Result.failed(SystemError.USER_1022);
        }
        try {
            int i = userService.resetPassword(u.getId(), req.getNewPassword());
            if (i > 0) {
                redisTemplate.delete(req.getKey());
            }
        } catch (Exception e) {
            return Result.failed(SystemError.USER_1014);
        }
        return Result.success();
    }

    @ApiOperation(value = "新增发送邮件重置初始密码", notes = "设置新密码-此方法无需登录通过邮箱认证")
    @OperationLogAnnotation(operModul = OperationLogModel.USER, operType = OperationLogType.UPDATE, operTitle = "新增发送邮件重置初始密码", operDesc = "重置密碼-此方法无需登录通过邮箱认证")
    @PostMapping("/reset/new/myPassword")
    public Result resetPass2(@RequestBody @Valid ResetNewMyPasswordReq req) {
        if (!redisTemplate.hasKey(req.getKey())) {
            return Result.failed(SystemError.USER_1025);
        }
        if (!StringUtils.equals(req.getNewPassword(), req.getConfirmPassword())) {
            return Result.failed(SystemError.USER_1012);
        }
        //取出缓存中的用户id
        Object id = redisTemplate.opsForValue().get(req.getKey());
        UserDTO u = userService.loadUserByParam(id, 1);
        if (BCrypt.checkpw(req.getNewPassword(), u.getPassword())) {
            return Result.failed(SystemError.USER_1013);
        }
        if (!userService.verifyTheFormat(req.getNewPassword())) {
            return Result.failed(SystemError.USER_1022);
        }
        try {
            int i = userService.resetPassword(u.getId(), req.getNewPassword());
            if (i > 0) {
                redisTemplate.delete(req.getKey());
            }
        } catch (Exception e) {
            return Result.failed(SystemError.USER_1014);
        }
        return Result.success();
    }

    @ApiOperation(value = "验证码", notes = "登录验证码")
    @GetMapping("/code")
    public Result<CaptchaDTO> code() {
        return validateService.getCode();
    }

    @ApiOperation(value = "发送邮箱", notes = "发送邮箱-忘记密码使用")
    @OperationLogAnnotation(operModul = OperationLogModel.USER, operType = OperationLogType.OTHER, operTitle = "发送邮箱", operDesc = "发送邮箱-忘记密码使用")
    @GetMapping("/mail/send")
    @ApiImplicitParam(name = "email", value = "账号邮箱")
    public Result mail(@RequestParam String email) {
        UserDTO userDTO = userService.loadUserByParam(email, 3);
        String code = RandomUtil.randomNumbers(4);
        String key = RedisConstants.EMAIL_CODE_KEY + userDTO.getId();
        redisTemplate.opsForValue().set(key, code, 10, TimeUnit.MINUTES);
        try {
            log.info(email + "验证码发送邮件。。。");
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        mailTemplate.sendHtmlMail(email,"香港中旅社雲平臺驗證碼信息通知",getTemplate(userDTO.getCnSurname() + userDTO.getCnName(),userDTO.getUsername(),code));
                    } catch (MessagingException e) {
                        throw new BusinessException(SystemError.SYS_429);
                    }
                }
            }).start();
        } catch (Exception me3) {
            log.error("发送验证码邮件错误，错误的信息：" + me3.getMessage());
        }
        return Result.success();
    }

    @ApiOperation(value = "验证邮箱", notes = "验证邮箱-验证邮箱校验码")
    @OperationLogAnnotation(operModul = OperationLogModel.USER, operType = OperationLogType.OTHER, operTitle = "发送邮箱", operDesc = "发送邮箱-忘记密码使用")
    @GetMapping("/mail/verify/code")
    @ApiImplicitParam(name = "email", value = "账号邮箱")
    public Result verify(@RequestParam String email, @RequestParam String code) {
        UserDTO userDTO = userService.loadUserByParam(email, 3);
        String key = RedisConstants.EMAIL_CODE_KEY + userDTO.getId();
        Object trueCode = redisTemplate.opsForValue().get(key);
        if (trueCode == null) {
            return Result.failed(SystemError.USER_1016);
        }
        if (!StringUtils.equals(code, trueCode.toString())) {
            return Result.failed(SystemError.USER_1017);
        }
        redisTemplate.delete(key);
        //创建一个15分钟修改密码的key
        String userKey = userDTO.getUsername() + RandomUtil.randomNumbers(4) + userDTO.getEmployeeNumber();
        String userKeyCode = MD5Util.stringMd5(userKey);
        redisTemplate.opsForValue().set(userKeyCode, userDTO.getId(), 15, TimeUnit.MINUTES);
        return Result.success(userKeyCode);
    }

    /**
     * 获取模板
     *
     * @param name      用户中文名称
     * @param loginName 登录名
     * @param code    验证码
     * @return
     */
    private String getTemplate(String name, String loginName, String code) {
        StringBuffer sb = new StringBuffer();
        sb.append("<!DOCTYPE HTML>\n");
        sb.append("<html lang=\"en-US\">\n");
        sb.append("<head>\n");
        sb.append("\t<meta charset=\"UTF-8\">\n");
        sb.append("\t<title>忘记密码</title>\n");
        sb.append("\t<style type=\"text/css\">\n");
        sb.append("\t\t.template{\n");
        sb.append("\t\t\twidth: 800px;\n");
        sb.append("\t\t\tmargin: 50px auto 0px auto;\n");
        sb.append("\t\t}\n");
        sb.append("\t\tp,a{\n");
        sb.append("\t\t\tpadding-left:2em;\n");
        sb.append("\t\t}\n");
        sb.append("\t</style>\n");
        sb.append("</head>\n");
        sb.append("<body>\n");
        sb.append("\t<div class=\"template\" >\n");
        sb.append("\t\t<h3>Dear <span>" + name + "</span>：</h3>\n");
        sb.append("\t\t<p>您好，您的賬戶"+loginName+" 的驗證碼是:"+code+"</p>\n");
        sb.append("\t\t<p>請在 15 分鐘內輸入，以免驗證碼失效。</p>\n");
        sb.append("\t\t<p>如非您本人操作，請及時聯繫系統管理員。</p>\n");
        sb.append("\t\t<br/>\n");
        sb.append("\t\t<p>Hello，Your account "+loginName+" verification code is "+code+".</p>\n");
        sb.append("\t\t<p>Please enter it within 15 minutes to prevent the verification code from becoming invalid.</p>\n");
        sb.append("\t\t<p>Please contact the system administrator in a timely manner if you are not the one operating the system.</p>\n");
        sb.append("\t</div>\n");
        sb.append("</body>\n");
        sb.append("</html>");
        return sb.toString();
    }
}
